home *** CD-ROM | disk | FTP | other *** search
/ Chip 2002 July / 07_02.iso / macos / files / Netscape-mac-full.bin / Netscape-mac-full / Netscape Full Installer / Installer Modules / browser.xpi / viewer / Components / nsProgressDialog.js < prev    next >
Text File  |  2002-05-12  |  32KB  |  825 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Mozilla Progress Dialog.
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  * Netscape Communications Corp.
  18.  * Portions created by the Initial Developer are Copyright (C) 2002
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Bill Law <law@netscape.com>
  23.  *
  24.  * Alternatively, the contents of this file may be used under the terms of
  25.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  26.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27.  * in which case the provisions of the GPL or the LGPL are applicable instead
  28.  * of those above. If you wish to allow use of your version of this file only
  29.  * under the terms of either the GPL or the LGPL, and not to allow others to
  30.  * use your version of this file under the terms of the MPL, indicate your
  31.  * decision by deleting the provisions above and replace them with the notice
  32.  * and other provisions required by the GPL or the LGPL. If you do not delete
  33.  * the provisions above, a recipient may use your version of this file under
  34.  * the terms of any one of the MPL, the GPL or the LGPL.
  35.  *
  36.  * ***** END LICENSE BLOCK ***** */
  37.  
  38. /* This file implements the nsIProgressDialog interface.  See nsIProgressDialog.idl
  39.  *
  40.  * The implementation consists of a JavaScript "class" named nsProgressDialog,
  41.  * comprised of:
  42.  *   - a JS constructor function
  43.  *   - a prototype providing all the interface methods and implementation stuff
  44.  *
  45.  * In addition, this file implements an nsIModule object that registers the
  46.  * nsProgressDialog component.
  47.  */
  48.  
  49. /* ctor
  50.  */
  51. function nsProgressDialog() {
  52.     // Initialize data properties.
  53.     this.mParent      = null;
  54.     this.mOperation   = null;
  55.     this.mStartTime   = ( new Date() ).getTime();
  56.     this.observer     = null;
  57.     this.mLastUpdate  = Number.MIN_VALUE; // To ensure first onProgress causes update.
  58.     this.mInterval    = 750; // Default to .75 seconds.
  59.     this.mElapsed     = 0;
  60.     this.mLoaded      = false;
  61.     this.fields       = new Array;
  62.     this.strings      = new Array;
  63.     this.mSource      = null;
  64.     this.mTarget      = null;
  65.     this.mApp         = null;
  66.     this.mDialog      = null;
  67.     this.mDisplayName = null;
  68.     this.mPaused      = null;
  69.     this.mRequest     = null;
  70.     this.mCompleted   = false;
  71.     this.mMode        = "normal";
  72.     this.mPercent     = 0;
  73.     this.mRate        = 0;
  74.     this.mBundle      = null;
  75. }
  76.  
  77. const nsIProgressDialog = Components.interfaces.nsIProgressDialog;
  78.  
  79. nsProgressDialog.prototype = {
  80.     // Turn this on to get debugging messages.
  81.     debug: false,
  82.  
  83.     // Chrome-related constants.
  84.     dialogChrome:   "chrome://global/content/nsProgressDialog.xul",
  85.     dialogFeatures: "chrome,titlebar,minimizable=yes",
  86.  
  87.     // getters/setters
  88.     get saving()            { return this.openingWith == null || this.openingWith == ""; },
  89.     get parent()            { return this.mParent; },
  90.     set parent(newval)      { return this.mParent = newval; },
  91.     get operation()         { return this.mOperation; },
  92.     set operation(newval)   { return this.mOperation = newval; },
  93.     get observer()          { return this.mObserver; },
  94.     set observer(newval)    { return this.mObserver = newval; },
  95.     get startTime()         { return this.mStartTime; },
  96.     set startTime(newval)   { return this.mStartTime = newval/1000; }, // PR_Now() is in microseconds, so we convert.
  97.     get lastUpdate()        { return this.mLastUpdate; },
  98.     set lastUpdate(newval)  { return this.mLastUpdate = newval; },
  99.     get interval()          { return this.mInterval; },
  100.     set interval(newval)    { return this.mInterval = newval; },
  101.     get elapsed()           { return this.mElapsed; },
  102.     set elapsed(newval)     { return this.mElapsed = newval; },
  103.     get loaded()            { return this.mLoaded; },
  104.     set loaded(newval)      { return this.mLoaded = newval; },
  105.     get source()            { return this.mSource; },
  106.     set source(newval)      { return this.mSource = newval; },
  107.     get target()            { return this.mTarget; },
  108.     set target(newval)      { return this.mTarget = newval; },
  109.     get openingWith()       { return this.mApp; },
  110.     set openingWith(newval) { return this.mApp = newval; },
  111.     get dialog()            { return this.mDialog; },
  112.     set dialog(newval)      { return this.mDialog = newval; },
  113.     get displayName()       { return this.mDisplayName; },
  114.     set displayName(newval) { return this.mDisplayName = newval; },
  115.     get paused()            { return this.mPaused; },
  116.     get request()           { return this.mRequest; },
  117.     get completed()         { return this.mCompleted; },
  118.     get mode()              { return this.mMode; },
  119.     get percent()           { return this.mPercent; },
  120.     get rate()              { return this.mRate; },
  121.     get kRate()             { return this.mRate / 1024; },
  122.  
  123.     // These setters use functions that update the dialog.
  124.     set paused(newval)      { return this.setPaused(newval); },
  125.     set request(newval)     { return this.setRequest(newval); },
  126.     set completed(newval)   { return this.setCompleted(newval); },
  127.     set mode(newval)        { return this.setMode(newval); },
  128.     set percent(newval)     { return this.setPercent(newval); },
  129.     set rate(newval)        { return this.setRate(newval); },
  130.  
  131.     // ---------- nsIProgressDialog methods ----------
  132.  
  133.     // open: Store aParentWindow and open the dialog.
  134.     open: function( aParentWindow ) {
  135.         // Save parent and "persist" operation.
  136.         this.parent    = aParentWindow;
  137.  
  138.         // Open dialog using the WindowWatcher service.
  139.         var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  140.                    .getService( Components.interfaces.nsIWindowWatcher );
  141.         this.dialog = ww.openWindow( this.parent,
  142.                                      this.dialogChrome,
  143.                                      null,
  144.                                      this.dialogFeatures,
  145.                                      this );
  146.     },
  147.     
  148.     init: function( aSource, aTarget, aDisplayName, aOpeningWith, aStartTime, aOperation ) {
  149.       this.source = aSource;
  150.       this.target = aTarget;
  151.       this.displayName = aDisplayName;
  152.       this.openingWith = aOpeningWith;
  153.       if ( aStartTime ) {
  154.           this.startTime = aStartTime;
  155.       }
  156.       this.operation = aOperation;
  157.     },
  158.  
  159.     // ---------- nsIWebProgressListener methods ----------
  160.  
  161.     // Look for STATE_STOP and update dialog to indicate completion when it happens.
  162.     onStateChange: function( aWebProgress, aRequest, aStateFlags, aStatus ) {
  163.         if ( aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP ) {
  164.             // we are done downloading...
  165.             this.completed = true;
  166.         }
  167.     },
  168.  
  169.     // Handle progress notifications.
  170.     onProgressChange: function( aWebProgress,
  171.                                 aRequest,
  172.                                 aCurSelfProgress,
  173.                                 aMaxSelfProgress,
  174.                                 aCurTotalProgress,
  175.                                 aMaxTotalProgress ) {
  176.         // Remember the request; this will also initialize the pause/resume stuff.
  177.         this.request = aRequest;
  178.  
  179.         var overallProgress = aCurTotalProgress;
  180.  
  181.         // Get current time.
  182.         var now = ( new Date() ).getTime();
  183.  
  184.         // If interval hasn't elapsed, ignore it.
  185.         if ( now - this.lastUpdate < this.interval &&
  186.              aMaxTotalProgress != "-1" && 
  187.              parseInt( aCurTotalProgress ) < parseInt( aMaxTotalProgress ) ) {
  188.             return;
  189.         }
  190.  
  191.         // Update this time.
  192.         this.lastUpdate = now;
  193.  
  194.         // Update elapsed time.
  195.         this.elapsed = now - this.startTime;
  196.  
  197.         // Calculate percentage.
  198.         if ( aMaxTotalProgress > 0) {
  199.             this.percent = Math.floor( ( overallProgress * 100.0 ) / aMaxTotalProgress );
  200.         } else {
  201.             this.percent = -1;
  202.         }
  203.  
  204.         // If dialog not loaded, then don't bother trying to update display.
  205.         if ( !this.loaded ) {
  206.             return;
  207.         }
  208.  
  209.         // Update dialog's display of elapsed time.
  210.         this.setValue( "timeElapsed", this.formatSeconds( this.elapsed / 1000 ) );
  211.  
  212.         // Now that we've set the progress and the time, update # bytes downloaded...
  213.         // Update status (nnK of mmK bytes at xx.xK aCurTotalProgress/sec)
  214.         var status = this.getString( "progressMsg" );
  215.  
  216.         // Insert 1 is the number of kilobytes downloaded so far.
  217.         status = this.replaceInsert( status, 1, parseInt( overallProgress/1024 + .5 ) );
  218.  
  219.         // Insert 2 is the total number of kilobytes to be downloaded (if known).
  220.         if ( aMaxTotalProgress != "-1" ) {
  221.             status = this.replaceInsert( status, 2, parseInt( aMaxTotalProgress/1024 + .5 ) );
  222.         } else {
  223.             status = replaceInsert( status, 2, "??" );
  224.         }
  225.     
  226.         // Insert 3 is the download rate.
  227.         if ( this.elapsed ) {
  228.             this.rate = ( aCurTotalProgress * 1000 ) / this.elapsed;
  229.             status = this.replaceInsert( status, 3, this.rateToKRate( this.rate ) );
  230.         } else {
  231.             // Rate not established, yet.
  232.             status = this.replaceInsert( status, 3, "??.?" );
  233.         }
  234.     
  235.         // All 3 inserts are taken care of, now update status msg.
  236.         this.setValue( "status", status );
  237.     
  238.         // Update time remaining.
  239.         if ( this.rate && ( aMaxTotalProgress > 0 ) ) {
  240.             // Calculate how much time to download remaining at this rate.
  241.             var rem = Math.round( ( aMaxTotalProgress - aCurTotalProgress ) / this.rate );
  242.             this.setValue( "timeLeft", this.formatSeconds( rem ) );
  243.         } else {
  244.             // We don't know how much time remains.
  245.             this.setValue( "timeLeft", this.getString( "unknownTime" ) );
  246.         }
  247.     },
  248.  
  249.     // Look for error notifications and display alert to user.
  250.     onStatusChange: function( aWebProgress, aRequest, aStatus, aMessage ) {
  251.         // Check for error condition (only if dialog is still open).
  252.         if ( aStatus != Components.results.NS_OK ) {
  253.             if ( this.loaded ) {
  254.                 // Get prompt service.
  255.                 var prompter = Components.classes[ "@mozilla.org/embedcomp/prompt-service;1" ]
  256.                                    .getService( Components.interfaces.nsIPromptService );
  257.                 // Display error alert (using text supplied by back-end).
  258.                 var title = this.getProperty( this.saving ? "savingAlertTitle" : "openingAlertTitle",
  259.                                               [ this.fileName() ], 
  260.                                               1 );
  261.                 prompter.alert( this.dialog, title, aMessage );
  262.     
  263.                 // Close the dialog.
  264.                 if ( !this.completed ) {
  265.                     this.onCancel();
  266.                 }
  267.             } else {
  268.                 // Error occurred prior to onload even firing.
  269.                 // We can't handle this error until we're done loading, so
  270.                 // defer the handling of this call.
  271.                 this.dialog.setTimeout( function(obj,wp,req,stat,msg){obj.onStatusChange(wp,req,stat,msg)},
  272.                                         100, this, aWebProgress, aRequest, aStatus, aMessage );
  273.             }
  274.         }
  275.     },
  276.  
  277.     // Ignore onLocationChange and onSecurityChange notifications.
  278.     onLocationChange: function( aWebProgress, aRequest, aLocation ) {
  279.     },
  280.  
  281.     onSecurityChange: function( aWebProgress, aRequest, state ) {
  282.     },
  283.  
  284.     // ---------- nsIObserver methods ----------
  285.     observe: function( anObject, aTopic, aData ) {
  286.         // Something of interest occured on the dialog.
  287.         // Dispatch to corresponding implementation method.
  288.         switch ( aTopic ) {
  289.         case "onload":
  290.             this.onLoad();
  291.             break;
  292.         case "oncancel":
  293.             this.onCancel();
  294.             break;
  295.         case "onpause":
  296.             this.onPause();
  297.             break;
  298.         case "onlaunch":
  299.             this.onLaunch();
  300.             break;
  301.         case "onreveal":
  302.             this.onReveal();
  303.             break;
  304.         case "onunload":
  305.             this.onUnload();
  306.             break;
  307.         case "oncompleted":
  308.             // This event comes in when setCompleted needs to be deferred because
  309.             // the dialog isn't loaded yet.
  310.             this.completed = true;
  311.             break;
  312.         default:
  313.             break;
  314.         }
  315.     },
  316.  
  317.     // ---------- nsISupports methods ----------
  318.  
  319.     // This "class" supports nsIProgressDialog, nsIWebProgressListener (by virtue
  320.     // of interface inheritance), nsIObserver, and nsISupports.
  321.     QueryInterface: function (iid) {
  322.         if (!iid.equals(Components.interfaces.nsIProgressDialog) &&
  323.             !iid.equals(Components.interfaces.nsIDownload) && 
  324.             !iid.equals(Components.interfaces.nsIWebProgressListener) &&
  325.             !iid.equals(Components.interfaces.nsIObserver) &&
  326.             !iid.equals(Components.interfaces.nsISupports)) {
  327.             throw Components.results.NS_ERROR_NO_INTERFACE;
  328.         }
  329.         return this;
  330.     },
  331.  
  332.     // ---------- implementation methods ----------
  333.  
  334.     // Initialize the dialog.
  335.     onLoad: function() {
  336.         // Note that onLoad has finished.
  337.         this.loaded = true;
  338.  
  339.         // Fill dialog.
  340.         this.loadDialog();
  341.  
  342.         // Position dialog.
  343.         if ( this.dialog.opener ) {
  344.             this.dialog.moveToAlertPosition();
  345.         } else {
  346.             this.dialog.centerWindowOnScreen();
  347.         }
  348.  
  349.         // Set initial focus on "keep open" box.  If that box is hidden, or, if
  350.         // the download is already complete, then focus is on the cancel/close
  351.         // button.  The download may be complete if it was really short and the
  352.         // dialog took longer to open than to download the data.
  353.         if ( !this.completed && !this.saving ) {
  354.             this.dialogElement( "keep" ).focus();
  355.         } else {
  356.             this.dialogElement( "cancel" ).focus();
  357.         }
  358.     },
  359.  
  360.     // load dialog with initial contents
  361.     loadDialog: function() {
  362.         // Check whether we're saving versus opening with a helper app.
  363.         if ( !this.saving ) {
  364.             // Put proper label on source field.
  365.             this.setValue( "sourceLabel", this.getString( "openingSource" ) );
  366.  
  367.             // Target is the "opening with" application.  Hide if empty.
  368.             if ( this.openingWith.length == 0 ) {
  369.                 this.hide( "targetRow" );
  370.             } else {
  371.                 // Use the "with:" label.
  372.                 this.setValue( "targetLabel", this.getString( "openingTarget" ) );
  373.                 // Name of application.
  374.                 this.setValue( "target", this.openingWith );
  375.             }
  376.         } else {
  377.             // Target is the destination file.
  378.             this.setValue( "target", this.target.unicodePath );
  379.         }
  380.  
  381.         // Set source field.
  382.         this.setValue( "source", this.source.spec );
  383.  
  384.         var now = ( new Date() ).getTime();
  385.  
  386.         // Intialize the elapsed time.
  387.         if ( !this.elapsed ) {
  388.             this.elapsed = now - this.startTime;
  389.         }
  390.  
  391.         // Update elapsed time display.
  392.         this.setValue( "timeElapsed", this.formatSeconds( this.elapsed / 1000 ) );
  393.         this.setValue( "timeLeft", this.getString( "unknownTime" ) );
  394.  
  395.         // Initialize the "keep open" box.  Hide this if we're opening a helper app.
  396.         if ( !this.saving ) {
  397.             // Hide this in this case.
  398.             this.hide( "keep" );
  399.         } else {
  400.             // Initialize using last-set value from prefs.
  401.             var prefs = Components.classes[ "@mozilla.org/preferences-service;1" ]
  402.                             .getService( Components.interfaces.nsIPrefBranch );
  403.             if ( prefs ) {
  404.                 this.dialogElement( "keep" ).checked = prefs.getBoolPref( "browser.download.progressDnldDialog.keepAlive" );
  405.             }
  406.         }
  407.  
  408.         // Initialize title.
  409.         this.setTitle();
  410.     },
  411.  
  412.     // Cancel button stops the download (if not completed),
  413.     // and closes the dialog.
  414.     onCancel: function() {
  415.          // Cancel the download, if not completed.
  416.          if ( !this.completed ) {
  417.              if ( this.operation ) {
  418.                  this.operation.cancelSave();
  419.                  // XXX We're supposed to clean up files/directories.
  420.              }
  421.              if ( this.observer ) {
  422.                  this.observer.observe( this, "oncancel", "" );
  423.              }
  424.              this.paused = false;
  425.          }
  426.         // Test whether the dialog is already closed.
  427.         // This will be the case if we've come through onUnload.
  428.         if ( this.dialog ) {
  429.             // Close the dialog.
  430.             this.dialog.close();
  431.         }
  432.     },
  433.  
  434.     // onunload event means the dialog has closed.
  435.     // We go through our onCancel logic to stop the download if still in progress.
  436.     onUnload: function() {
  437.         // Remember "keep dialog open" setting, if visible.
  438.         if ( this.saving ) {
  439.             var prefs = Components.classes["@mozilla.org/preferences-service;1"]
  440.                           .getService( Components.interfaces.nsIPrefBranch );
  441.             if ( prefs ) {
  442.                 prefs.setBoolPref( "browser.download.progressDnldDialog.keepAlive", this.dialogElement( "keep" ).checked );
  443.             }
  444.          }
  445.          this.dialog = null; // The dialog is history.
  446.          this.onCancel();
  447.     },
  448.  
  449.     // onpause event means the user pressed the pause/resume button
  450.     // Toggle the pause/resume state (see the function setPause(), below).i
  451.     onPause: function() {
  452.          this.paused = !this.paused;
  453.     },
  454.  
  455.     // onlaunch event means the user pressed the launch button
  456.     // Invoke the launch method of the target file.
  457.     onLaunch: function() {
  458.          try {
  459.              this.target.launch();
  460.              this.dialog.close();
  461.          } catch ( exception ) {
  462.              // XXX Need code here to tell user the launch failed!
  463.              dump( "nsProgressDialog::onLaunch failed: " + exception + "\n" );
  464.          }
  465.     },
  466.  
  467.     // onreveal event means the user pressed the "reveal location" button
  468.     // Invoke the reveal method of the target file.
  469.     onReveal: function() {
  470.          try {
  471.              this.target.reveal();
  472.              this.dialog.close();
  473.          } catch ( exception ) {
  474.          }
  475.     },
  476.  
  477.     // Get filename from target file.
  478.     fileName: function() {
  479.         return this.target ? this.target.unicodeLeafName : "";
  480.     },
  481.  
  482.     // Set the dialog title.
  483.     setTitle: function() {
  484.         // Start with saving/opening template.
  485.         var title = this.saving ? this.getString( "savingTitle" ) : this.getString( "openingTitle" );
  486.  
  487.         // Use file name as insert 1.
  488.         title = this.replaceInsert( title, 1, this.fileName() );
  489.  
  490.         // Use percentage as insert 2.
  491.         title = this.replaceInsert( title, 2, this.percent );
  492.  
  493.         // Set <window>'s title attribute.
  494.         if ( this.dialog ) {
  495.             this.dialog.title = title;
  496.         }
  497.     },
  498.  
  499.     // Update the dialog to indicate specified percent complete.
  500.     setPercent: function( percent ) {
  501.         // Maximum percentage is 100.
  502.         if ( percent > 100 ) {
  503.             percent = 100;
  504.         }
  505.         // Test if percentage is changing.
  506.         if ( this.percent != percent ) {
  507.             this.mPercent = percent;
  508.  
  509.             // If dialog not opened yet, bail early.
  510.             if ( !this.loaded ) {
  511.                 return this.mPercent;
  512.             }
  513.  
  514.             if ( percent == -1 ) {
  515.                 // Progress meter needs to be in "undetermined" mode.
  516.                 this.mode = "undetermined";
  517.  
  518.                 // Update progress meter percentage text.
  519.                 this.setValue( "progressText", "" );
  520.             } else {
  521.                 // Progress meter needs to be in normal mode.
  522.                 this.mode = "normal";
  523.  
  524.                 // Set progress meter thermometer.
  525.                 this.setValue( "progress", percent );
  526.  
  527.                 // Update progress meter percentage text.
  528.                 this.setValue( "progressText", this.replaceInsert( this.getString( "percentMsg" ), 1, percent ) );
  529.             }
  530.     
  531.             // Update title.
  532.             this.setTitle();
  533.         }
  534.         return this.mPercent;
  535.     },
  536.  
  537.     // Update download rate and dialog display.
  538.     // Note that we don't want the displayed value to quiver
  539.     // between essentially identical values (e.g., 99.9Kb and
  540.     // 100.0Kb) so we only update if we see a big change.
  541.     setRate: function( rate ) {
  542.         if ( rate ) {
  543.             // rate is bytes/sec
  544.             var change = Math.abs( this.rate - rate );
  545.             // Don't update too often!
  546.             if ( change > this.rate / 10 ) {
  547.                 // Displayed rate changes.
  548.                 this.mRate = rate;
  549.             }
  550.         }
  551.         return this.mRate;
  552.     },
  553.  
  554.     // Handle download completion.
  555.     setCompleted: function() {
  556.         // If dialog hasn't loaded yet, defer this.
  557.         if ( !this.loaded ) {
  558.             this.dialog.setTimeout( function(obj){obj.setCompleted()}, 100, this );
  559.             return false;
  560.         }
  561.         if ( !this.mCompleted ) {
  562.             this.mCompleted = true;
  563.  
  564.             // If the "keep dialog open" box is checked, then update dialog.
  565.             if ( this.dialog && this.dialogElement( "keep" ).checked ) {
  566.                 // Indicate completion in status area.
  567.                 this.setValue( "status", this.replaceInsert( this.getString( "completeMsg" ),
  568.                                                              1,
  569.                                                              this.formatSeconds( this.elapsed/1000 ) ) );
  570.  
  571.                 // Put progress meter at 100%.
  572.                 this.percent = 100;
  573.  
  574.                 // Set time remaining to 00:00.
  575.                 this.setValue( "timeLeft", this.formatSeconds( 0 ) );
  576.  
  577.                 // Change Cancel button to Close, and give it focus.
  578.                 var cancelButton = this.dialogElement( "cancel" );
  579.                 cancelButton.label = this.getString( "close" );
  580.                 cancelButton.focus();
  581.  
  582.                 // Activate reveal/launch buttons.
  583.                 this.enable( "reveal" );
  584.                 if ( this.target && !this.target.isExecutable() ) {
  585.                     this.enable( "launch" );
  586.                 }
  587.  
  588.                 // Disable the Pause/Resume buttons.
  589.                 this.dialogElement( "pauseResume" ).disabled = true;
  590.  
  591.                 // Fix up dialog layout (which gets messed up sometimes).
  592.                 this.dialog.sizeToContent();
  593.             } else if ( this.dialog ) {
  594.                 this.dialog.close();
  595.             }
  596.         }
  597.         return this.mCompleted;
  598.     },
  599.  
  600.     // Set progress meter to given mode ("normal" or "undetermined").
  601.     setMode: function( newMode ) {
  602.         if ( this.mode != newMode ) {
  603.             // Need to update progress meter.
  604.             this.dialogElement( "progress" ).setAttribute( "mode", newMode );
  605.         }
  606.         return this.mMode = newMode;
  607.     },
  608.  
  609.     // Set pause/resume state.
  610.     setPaused: function( pausing ) {
  611.         // If state changing, then update stuff.
  612.         if ( this.paused != pausing ) {
  613.             var string = pausing ? "resume" : "pause";
  614.             this.dialogElement( "pauseResume" ).label = this.getString(string);
  615.  
  616.             // If we have a request, suspend/resume it.
  617.             if ( this.request ) {
  618.                 if ( pausing ) {
  619.                     this.request.suspend();
  620.                 } else {
  621.                     this.request.resume();
  622.                 }
  623.             }
  624.         }
  625.         return this.mPaused = pausing;
  626.     },
  627.  
  628.     // Set the saved nsIRequest.  The first time, we test it for
  629.     // ftp and initialize the pause/resume stuff.
  630.     // XXX This is broken, I think, because if we're doing something
  631.     //     like saving a web-page-complete that has multiple images
  632.     //     accessed via ftp: urls, then it seems like the pause/resume
  633.     //     button should come and go, depending on what's being downloaded
  634.     //     at a given point in time.  The old dialog didn't handle that case
  635.     //     either, though, so I'm not sweating it for now.
  636.     setRequest: function( aRequest ) {
  637.         if ( this.request == null && this.loaded && aRequest ) {
  638.             // Right now, all that supports restarting downloads is ftp (rfc959).
  639.             try {
  640.                 ftpChannel = aRequest.QueryInterface( Components.interfaces.nsIFTPChannel );
  641.                 if ( ftpChannel ) {
  642.                     this.dialogElement("pauseResume").label = this.getString("pause");
  643.                     this.paused = false;
  644.                 }
  645.             } catch ( e ) {
  646.             }
  647.             // This *must* come after the "this.paused = false" above, so that we
  648.             // don't suspend or resume the first time we call that function!
  649.             this.mRequest = aRequest;
  650.         }
  651.         return this.mRequest;
  652.     },
  653.  
  654.     // Convert raw rate (bytes/sec) to Kbytes/sec (to nearest tenth).
  655.     rateToKRate: function( rate ) {
  656.          var kRate = rate / 1024; // KBytes/sec
  657.          var fraction = parseInt( ( kRate - ( kRate = parseInt( kRate ) ) ) * 10 + 0.5 );
  658.          return kRate + "." + fraction;
  659.     },
  660.  
  661.     // Format number of seconds in hh:mm:ss form.
  662.     formatSeconds: function( secs ) {
  663.         // Round the number of seconds to remove fractions.
  664.         secs = parseInt( secs + .5 );
  665.         var hours = parseInt( secs/3600 );
  666.         secs -= hours*3600;
  667.         var mins = parseInt( secs/60 );
  668.         secs -= mins*60;
  669.         var result;
  670.         if ( hours )
  671.             result = this.getString( "longTimeFormat" );
  672.         else
  673.             result = this.getString( "shortTimeFormat" );
  674.     
  675.         if ( hours < 10 )
  676.             hours = "0" + hours;
  677.         if ( mins < 10 )
  678.             mins = "0" + mins;
  679.         if ( secs < 10 )
  680.             secs = "0" + secs;
  681.     
  682.         // Insert hours, minutes, and seconds into result string.
  683.         result = this.replaceInsert( result, 1, hours );
  684.         result = this.replaceInsert( result, 2, mins );
  685.         result = this.replaceInsert( result, 3, secs );
  686.     
  687.         return result;
  688.     },
  689.  
  690.     // Get dialog element using argument as id.
  691.     dialogElement: function( id ) {
  692.         // Check if we've already fetched it.
  693.         if ( !( id in this.fields ) ) {
  694.             // No, then get it from dialog.
  695.             try {
  696.                 this.fields[ id ] = this.dialog.document.getElementById( id );
  697.             } catch(e) {
  698.                 this.fields[ id ] = { 
  699.                     value: "",
  700.                     setAttribute: function(id,val) {},
  701.                     removeAttribute: function(id) {}
  702.                     }
  703.             }
  704.         }
  705.         return this.fields[ id ];
  706.     },
  707.  
  708.     // Set dialog element value for given dialog element.
  709.     setValue: function( id, val ) {
  710.         this.dialogElement( id ).value = val;
  711.     },
  712.  
  713.     // Enable dialgo element.
  714.     enable: function( field ) {
  715.         this.dialogElement( field ).removeAttribute( "disabled" );
  716.     },
  717.  
  718.     // Get localizable string from properties file.
  719.     getProperty: function( propertyId, strings, len ) {
  720.         if ( !this.mBundle ) {
  721.             this.mBundle = Components.classes[ "@mozilla.org/intl/stringbundle;1" ]
  722.                              .getService( Components.interfaces.nsIStringBundleService )
  723.                                .createBundle( "chrome://global/locale/nsProgressDialog.properties");
  724.         }
  725.         return this.mBundle.formatStringFromName( propertyId, strings, len );
  726.     },
  727.  
  728.     // Get localizable string (from dialog <data> elements).
  729.     getString: function ( stringId ) {
  730.         // Check if we've fetched this string already.
  731.         if ( !( this.strings && stringId in this.strings ) ) {
  732.             // Presume the string is empty if we can't get it.
  733.             this.strings[ stringId ] = "";
  734.             // Try to get it.
  735.             try {
  736.                 this.strings[ stringId ] = this.dialog.document.getElementById( "string."+stringId ).childNodes[0].nodeValue;
  737.             } catch (e) {}
  738.        }
  739.        return this.strings[ stringId ];
  740.     },
  741.     
  742.     // Replaces insert ("#n") with input text.
  743.     replaceInsert: function( text, index, value ) {
  744.         var result = text;
  745.         var regExp = new RegExp( "#"+index );
  746.         result = result.replace( regExp, value );
  747.         return result;
  748.     },
  749.  
  750.     // Hide a given dialog field.
  751.     hide: function( field ) {
  752.         this.dialogElement( field ).setAttribute( "style", "display: none;" );
  753.         // Hide the associated separator, too.
  754.         this.dialogElement( field+"Separator" ).setAttribute( "style", "display: none;" );
  755.     },
  756.  
  757.     // Return input in hex, prepended with "0x" and leading zeros (to 8 digits).
  758.     hex: function( x ) {
  759.          var hex = Number(x).toString(16);
  760.          return "0x" + ( "00000000" + hex ).substring( hex.length );
  761.     },
  762.  
  763.     // Dump text (if debug is on).
  764.     dump: function( text ) {
  765.         if ( this.debug ) {
  766.             dump( text );
  767.         }
  768.     }
  769. }
  770.  
  771. // This Component's module implementation.  All the code below is used to get this
  772. // component registered and accessible via XPCOM.
  773. var module = {
  774.     // registerSelf: Register this component.
  775.     registerSelf: function (compMgr, fileSpec, location, type) {
  776.         compMgr = compMgr.QueryInterface( Components.interfaces.nsIComponentManagerObsolete );
  777.         compMgr.registerComponentWithType( this.cid,
  778.                                            "Mozilla Download Progress Dialog",
  779.                                            this.contractId,
  780.                                            fileSpec,
  781.                                            location,
  782.                                            true,
  783.                                            true,
  784.                                            type );
  785.     },
  786.  
  787.     // getClassObject: Return this component's factory object.
  788.     getClassObject: function (compMgr, cid, iid) {
  789.         if (!cid.equals(this.cid))
  790.             throw Components.results.NS_ERROR_NO_INTERFACE;
  791.  
  792.         if (!iid.equals(Components.interfaces.nsIFactory))
  793.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  794.  
  795.         return this.factory;
  796.     },
  797.  
  798.     /* CID for this class */
  799.     cid: Components.ID("{F5D248FD-024C-4f30-B208-F3003B85BC92}"),
  800.  
  801.     /* Contract ID for this class */
  802.     contractId: "@mozilla.org/progressdialog;1",
  803.  
  804.     /* factory object */
  805.     factory: {
  806.         // createInstance: Return a new nsProgressDialog object.
  807.         createInstance: function (outer, iid) {
  808.             if (outer != null)
  809.                 throw Components.results.NS_ERROR_NO_AGGREGATION;
  810.  
  811.             return (new nsProgressDialog()).QueryInterface(iid);
  812.         }
  813.     },
  814.  
  815.     // canUnload: n/a (returns true)
  816.     canUnload: function(compMgr) {
  817.         return true;
  818.     }
  819. };
  820.  
  821. // NSGetModule: Return the nsIModule object.
  822. function NSGetModule(compMgr, fileSpec) {
  823.     return module;
  824. }
  825.